#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Math;
using Weazel.Gezel.Simulator.Reference.Profiling;

#endregion

namespace Weazel.Gezel.Simulator.Reference
{
    public class Signal : AbstractSignal
    {
        /// <summary>
        /// The associated SignalContext instance
        /// is used to determine which signal 
        /// drives this instance in for a
        /// particular simulation cycle
        /// </summary>
        private readonly SignalContext context;

        /// <summary>
        /// If this signal is driven by stimuli or outport, this
        /// field store the reference the the Signal instance
        /// </summary>
        private AbstractSignal driver;

        /// <summary>
        /// Caches the value from signal evaluation
        /// </summary>
        private Integer value;

        /// <summary>
        /// True iff the signal was
        /// </summary>
        private readonly bool isUserDefined;
        public bool IsUserDefined
        {
            get
            {
                return isUserDefined;
            }
        }

        private Profiler profiler;

        /// <summary>
        /// Constructs a new Signal contained in the datapath
        /// with the specified datapath context
        /// </summary>
        /// <param name="datapathContext">datapath context of owning datapath</param>
        /// <param name="id">signal id</param>
        /// <param name="width">bitwidth</param>
        /// <param name="sign"></param>
        /// <param name="name">fully quantified name of signal</param>
        public Signal(Simulation sim, DatapathContext datapathContext, Symid id, int width, int sign, string name, bool isUserDefined)
            : base(id, name)
        {
            this.context = new SignalContext(sim, datapathContext, id);
            this.driver = null;
            this.value = new Integer(width, sign == 1);
            this.isUserDefined = isUserDefined;

            if (sim.profiler != null)
            {
                profiler = sim.profiler;
                profiler.MakeOperation(this, Width());
            }
        }

        internal override void defineDriver(AbstractSignal artsignal)
        {
            this.driver = artsignal;
        }

        /// <summary>
        /// Register a new defining signal for the
        /// referenced signalflowgraph
        /// </summary>
        /// <param name="sfgId">sfg which activates definer</param>
        /// <param name="definingSignal">defining signal in referenced sfg</param>
        internal override void newDefiner(Symid sfgId, AbstractSignal definingSignal)
        {
            // check that signal is not driven by stimuli
            if (driver != null)
                throw new ApplicationException("signal is already driven");
            else
                context.NewDefiner(sfgId, definingSignal);
        }

        /// <summary>
        /// Evaluate and return value of signal in 
        /// current simulation cycle
        /// </summary>
        /// <param name="targetGeneration">current simulation cycle</param>
        /// <returns>signal value for current cycle</returns>
        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            if (driver != null)
            {
                // this is a driven signal, for which no assignments 
                // inside of an sfg (context) can be done.
                generation = targetGeneration;

                if (profiler != null)
                    profiler.EvaluateOperation(Id, value);

                value.ValueCopy(driver.Evaluate(ip, targetGeneration));
            }
            else if ((currentstate == EvaluationState.Stable) && (generation != targetGeneration))
            {
                // first-time visit
                currentstate = EvaluationState.Evaluation;
                Integer res = context.ComputeCurrentDefiner().Evaluate(ip, targetGeneration);
                value.ValueCopy(res);
                generation = targetGeneration; // can take arbitrary jumps
                currentstate = EvaluationState.Stable;
            }
            else if ((currentstate == EvaluationState.Evaluation) && (generation != targetGeneration))
            {
                //if (ip != null)
                //  return value;

                // second-time visit - must be combinational dependency
                throw new CombinatorialLoopException(this.Id, this.name);
            }

            return value;
        }

        /// <summary>
        /// Determines if signal needs evaluation in
        /// the specified simulation cycle
        /// </summary>
        /// <param name="g">simulation cycle</param>
        /// <returns>true iff the signal needs evaluation</returns>
        public override bool IsGeneration(long g)
        {
            return g == generation;
        }

        /// <summary>
        /// Assigns a value to the signal. This is done from
        ///   RegisterSignal,
        ///   OutputSignal and
        ///   AssignSignal
        /// </summary>
        /// <param name="nv">new signal value</param>
        internal override void Assign(Integer nv)
        {
            value.ValueCopy(nv);

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);
        }

        /// <summary>
        /// Output signal value to buffer using the current radix
        /// </summary>
        /// <param name="buffer"></param>
        public override void Show(StringBuilder buffer)
        {
            buffer.Append(value.ToString(@base));
        }

        /// <summary>
        /// Return the signal value without performing evaluation
        /// </summary>
        /// <returns></returns>
        internal override Integer PeekValue()
        {
            return value;
        }

        /// <summary>
        /// Bitwidth of signal
        /// </summary>
        /// <returns></returns>
        internal override uint Width()
        {
            return (uint)value.Width;
        }

        /// <summary>
        /// Determines if signal is signed
        /// </summary>
        /// <returns></returns>
        internal override uint Signed()
        {
            return value.Signed ? 1u : 0u;
        }

        /// <summary>
        /// Returns true iff signal has definer in 
        /// current simulation cycle. Deffered to
        /// contained SignalContext instance.
        /// </summary>
        /// <returns></returns>
        public bool HasDefiner()
        {
            return context.HasDefiner();
        }

        public override bool TraceToSignal(Symid toSignalId, List<AbstractSignal> signals)
        {
            // check if this is the signal in which
            // the combinatorial loop was detected
            if (this.Id == toSignalId && signals.Contains(this))
            {
                signals.Add(this);
                return true;
            }
            else if (driver != null)
            {
                signals.Add(this);
                return driver.TraceToSignal(toSignalId, signals);
            }
            else
            {
                signals.Add(this);
                AbstractSignal definer = context.ComputeCurrentDefiner();
                return definer.TraceToSignal(toSignalId, signals);
            }
        }

        /// <summary>
        /// For easier debugging
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return Name;
        }
    }
}